package com.thread.threadfirst.studyThread4;

import java.io.IOException;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

/**
 * 11、ForkJoinPool
 * Fork：分叉，Join：合并。
 * 一个大的任务，计算量特别大，时间特别长，就可以切成一个个的小任务，如果小任务还是太大，可以再继续切成一个个小任务，然后将所有任务的结果进行合并，
 * 产生一个总的结果，而切分的规则可以由你自己来定制，线程的启动和切分的规则由ForkJoinPool这个线程池自己来维护。
 * 计算100万个随机数的总和。
 * <p>
 * ForkJoinPool中的任务必须都是ForkJoinTask，这样的任务可以自动进行切分（是使用fork()来自动切分的） 一般都是继承ForkJoinTask类 但这样写起来很麻烦，
 * 所以一般是继承RecursiveAction（没有返回值）和RecursiveTask（递归任务 即切分后再切分切分后再切分，有返回值）这2个类。
 */
public class T_ThreadPool6 {

    //使用streamAPI一次性计算出总和。
    static int[] nums = new int[1000000];
    static final int MAX_NUM = 50000;
    static Random r = new Random();

    static {
        for (int i = 0; i < nums.length; i++) {
            nums[i] = r.nextInt(100);
        }

        System.out.println("static=" + Arrays.stream(nums).sum());  //streamAPI 有个直接求总和的sum()方法 注意：parallelStream使用了多线程，而stream没有使用多线程。
    }

    //使用ForkJoin的思想 将一个大任务依据切分的规则 fork切分成若干小任务来并发的执行，最后将每个任务的结果join合并，大幅提升了效率 减少了运行时间。
    //1、使用RecursiveAction(无返回值) 所以也就不能Join了。
    //为什么不直接写成匿名内部类 而是写一个类来继承RecursiveAction/RecursiveTask呢？因为匿名内部类不能有构造方法 所以也就不能传值，所以需要单独写一个类来继承RecursiveAction/RecursiveTask。
    /*
    public static class AddTask extends RecursiveAction {  //ForkJoinPool中的任务必须都是ForkJoinTask，而通常不直接继承ForkjoinTask类，只需要直接继承其子类：RecursiveAction/RecursiveTask。
        int start,end;

        public AddTask(int start, int end) {
            this.start = start;
            this.end = end;
        }

        //上面有100万个数字，将这100万个数字分成每个任务操作不超过5万个数的若干小线程来执行。
        @Override
        protected void compute() {
            if (end-start<=MAX_NUM) {  //如果每个任务操作的数小于等于5万，则计算。
                long sum=0;
                for (int i=start;i<end;i++) sum+=nums[i];
                System.out.println("from "+start+" to "+end+" = "+sum);
            }else {  //如果每个任务操作的数超过了5万，则继续切分任务。
                int middle=start+(end-start)/2;  //均分成2份小任务。
                AddTask subTask1=new AddTask(start,middle);  //生成任务。
                AddTask subTask2=new AddTask(middle,end);
                subTask1.fork();  //只要fork()，就有新的线程启动了。因为原本只有一个线程执行 这里fork()就分出来了一个线程执行subTask1这个任务。
                subTask2.fork();
            }
        }
    }
     */

    //2、使用RecursiveTask(有返回值)所以就可以Join了。
    public static class AddTask extends RecursiveTask<Long> {  //泛型规定返回值的类型。
        int start, end;

        public AddTask(int start, int end) {
            this.start = start;
            this.end = end;
        }

        @Override
        protected Long compute() {
            if (end - start <= MAX_NUM) {  //如果每个任务操作的数小于等于5万，则计算并返回sum。
                long sum = 0;
                for (int i = start; i < end; i++) sum += nums[i];
                return sum;
            }
            int middle = start + (end - start) / 2;  //均分成2份小任务。
            AddTask subTask1 = new AddTask(start, middle);  //生成任务。
            AddTask subTask2 = new AddTask(middle, end);
            subTask1.fork();  //只要fork()，就有新的线程启动了。因为原本只有一个线程执行 这里fork()就分出来了一个线程执行subTask1这个任务 自动执行compute()方法。
            subTask2.fork();

            return subTask1.join() + subTask2.join();  //最后将两个分任务的sum join汇总一下。
        }
    }

    //注意：如果此处的main()方法中什么也不写的话，上面的static中的输出还是会打印的，这是因为类加载的时候会执行static代码块。
    public static void main(String[] args) throws IOException {
        ForkJoinPool fjp = new ForkJoinPool();
        AddTask task = new AddTask(0, nums.length);
        fjp.execute(task);  //自动执行了RecursiveAction类的compute()。
        //1、使用RecursiveAction(无返回值)
        //因为ForkJoinPool是精灵线程（即后台线程 守护线程），所以需要主线程阻塞 才能看到精灵线程的输出。
//        System.in.read();
        //2、使用RecursiveTask(有返回值)
        long result = task.join();  //join()；本身就是阻塞的，只有等任务都执行完了 才得到最终的结果。
        System.out.println(result);
    }

}

